<html>
  <head>
    <title>用状态响应输入</title>
  </head>
  <body>
    <script>
      // 用状态响应输入
              /*
                  React控制UI的方式是声明式的。
                  你不必直接控制UI的各个部分，只需要声明组件可以处于的不同状态，并根据用户的输入在它们之间切换。
                  这与设计师对UI的思想方式很相似。
              */


              // 一、"声明式UI"和"命令式UI"的比较
                        /*
                            当你设计UI交互时，可能会去思考UI如何根据用户的操作而响应变化。想象一个允许用户提交一个答案的表单：

                                  1、当你向表单输入数据时，"提交"按钮随之变成"可用状态"。
                                  2、当你点击"提交"后，表单和提交按钮都会随之编程"不可用状态"，并且会加载动画会随之出现
                                  3、如果网络请求成功，表单会随之"隐藏"，同时"提交成功"的信息会随之出现
                                  4、如果网络请求失败，错误信息会随之"出现"，同时表单又变为"可用状态"

                             在"命令式编程"中，以上的过程直接告诉你如何去实现交互。
                             你必须去根据要发生的事情写一些明确的命令去操作UI。
                             对此，有另外一种理解方式。比如想象一下，当你坐在车里的某个人旁边，然后一步一步地告诉他该去哪里。
                             他并不知道你想去哪里，只想跟着命令行动(并且，如果你发出了错误的命令，那么你就会到大错误的地方)。
                             正因为你必须从加载动画到按钮地"命令"每个元素，所以这种"告诉计算机如何去更新UI"的编程方式称为"命令式编程"。
                        */
                             // 在这种"命令式编程"的例子中，表单没有使用React生成，而是使用原生的ＤＯＭ：
                             　　　  async function handleFormSubmit(e) {
                                          e.preventDefault();
                                          disable(textarea);
                                          disable(button);
                                          show(loadingMessage);
                                          hide(errorMessage);
                                          try {
                                            await submitForm(textarea.value);
                                            show(successMessage);
                                            hide(form);
                                          } catch (err) {
                                            show(errorMessage);
                                            errorMessage.textContent = err.message;
                                          } finally {
                                            hide(loadingMessage);
                                            enable(textarea);
                                            enable(button);
                                          }
                                      }

                                      function handleTextareaChange() {
                                          if (textarea.value.length === 0) {
                                            disable(button);
                                          } else {
                                            enable(button);
                                          }
                                      }

                                      function hide(el) {
                                          el.style.display = 'none';
                                      }

                                      function show(el) {
                                          el.style.display = '';
                                      }

                                      function enable(el) {
                                          el.disabled = false;
                                      }

                                      function disable(el) {
                                          el.disabled = true;
                                      }

                                      function submitForm(answer) {
                                        // Pretend it's hitting the network.
                                          return new Promise((resolve, reject) => {
                                            setTimeout(() => {
                                              if (answer.toLowerCase() === 'istanbul') {
                                                resolve();
                                              } else {
                                                reject(new Error('Good guess but a wrong answer. Try again!'));
                                              }
                                            }, 1500);
                                          });
                                      }

                                      let form = document.getElementById('form');
                                      let textarea = document.getElementById('textarea');
                                      let button = document.getElementById('button');
                                      let loadingMessage = document.getElementById('loading');
                                      let errorMessage = document.getElementById('error');
                                      let successMessage = document.getElementById('success');
                                      form.onsubmit = handleFormSubmit;
                                      textarea.oninput = handleTextareaChange;
                                      button.disabled = true; 
                                      
                                      
                              /*
                                  对于"独立系统"来说，命令式地控制用户界面的效果也不错，但是当处于复杂的系统中，这会造成管理的困难程度指数级地增长。
                                  如同实例一样，想象一下，当你想更新这样一个包含这不同表单的页面时，把你想要添加一个新的UI元素或一个新的交互，
                                  为了保证不会因此产生新的Bug，你必须十分小心地去检查所有已经写好的代码。

                                  React 正是为了解决这样的问题而诞生的。

                                  在React中，你不必直接去操作UI----你不必直接启用、关闭、显示或隐藏组件。
                                  相反，你只需要"声明你想要显示的内容",React就会通过计算得出该如何去更新UI。想象一下，当你上了一辆出租车，
                                  并且告诉司机你想去哪，而不是事无巨细地告诉他该如何走。将你带到目的地是司机的工作，它们甚至可能知道一些你没有想过且不知道的捷径！
                              */

              // 一、声明式地考虑UI
                               /*
                                    你已经从上面的例子看到如何去实现一个表单了，为了更好地理解如何在React中思考，
                                    接下来你将会学到如何用React如何重新实现这个UI：

                                        ①、定位你的组件中的不同的视图状态
                                        ②、确定是什么触发了这些状态的改变
                                        ③、表示内存中的状态(需要用useState)
                                        ④、删除任何不必要的状态变量
                                        ⑤、连接事件处理程序去设置状态
                               */      

                               // 1、步骤1： 定位组件中不同的视图状态
                                         /*
                                              在计算机科学中，你或许听过可处于多种"状态"之一的"状态机"。
                                              如果你有与UI设计师一起工作，那么你可能已经见过"不同视图状态"的模拟图。正因为React站在设计与计算机科学的交点上，
                                              因此这两种思想都是灵感的来源。

                                              首先，你需要去可视化UI界面中用户可能看到的所有不同的"状态"：

                                                  ①、无数据：  表单又一个不可用状态的"提交"按钮
                                                  ②、输入中：  表单又一个可用状态的"提交"按钮
                                                  ③、提交中：  表单完全处于不可用状态，加载动画出现
                                                  ④、成功时：  显示"成功"的消息而非表单
                                                  ⑤、错误时：  与输入状态相似，但会多了错误的消息。

                                              像一个UI设计师一样，你会想要在你添加逻辑之前去"模拟"不同的状态或创建"模拟状态"。
                                         */

                                          /*
                                              例如下面的例子，这是一个对表单可视部分的模拟。
                                              这个模拟被一个名为"status"的属性控制，并且这个属性的默认值为"empty"
                                          */ 
                                                  export default function From({status}){
                                                    if(status==='success'){
                                                      return (
                                                        <h1>That's right</h1>
                                                      );
                                                      return (
                                                        <>
                                                            <h2>City quiz</h2>
                                                            <p>
                                                              In which city is there a billboard that turns air into drinkable water?
                                                            </p>
                                                            <form>
                                                              <textarea/>
                                                              <br/>
                                                              <button>
                                                                Submit
                                                              </button>
                                                            </form>
                                                        </>
                                                      );
                                                    }
                                                  }

                                            /*
                                                你可以随意命名这些属性，名字并不重要。
                                                试着将 status ='empty' 改为 status ='success',然后你就会看到成功信息出现。
                                                模拟可以上你在书写逻辑前快速迭代UI。
                                            */
                                                  // eg: 这是同一组件的一个更充实的原型，仍然由status属性控制：
                                                  export default function Form({status}){
                                                    if(status==='success'){
                                                        return (
                                                              <h1>That's right</h1>
                                                        );
                                                    }
                                                    return (
                                                      <>
                                                          <h2>City quiz</h2>
                                                          <p>
                                                            In which city is there a billboard that turns air into drinkable water?
                                                          </p>
                                                          <Form>
                                                              <textarea disabled={
                                                                status==='submitting'
                                                              }/>

                                                               <button disabled={
                                                                  status==='empty'||
                                                                  status ==='success'
                                                               }>
                                                                   Submit
                                                                </button>
                                                                {status==='error'&&
                                                                  <p className="Error">
                                                                    Good guess but a wrong answer.Try again!
                                                                  </p>
                                                                }
                                                          </Form>
                                                      </>
                                                    );
                                                  }
                              
                              // 2、步骤2： 确定是什么触发了这些状态的改变
                                          /*
                                              你可以触发状态的更新来响应两种输入：
                                                    ①、认为输入： 比如 点击按钮、在表单中填写内容，或导航到连接
                                                    ②、计算机输入：  比如网络请求得到响应、定时器被触发，或加载一张图片


                                              以上两种情况中，你"必须设置状态去更新UI"。
                                              对于正在开发中的表单来说，你需要更改状态以响应几个不同的输入：

                                                   ①、改变输入框的文本时（人为）：  应该根据输入框的内容是否是空值，从而决定将表单的状态从"空值状态"切换到"输入中"状态
                                                   ②、点击提交按钮时（人为）：      应该将表单的状态切换到"提交中"状态
                                                   ③、网络请求成功后(计算机)：      应该将表单的状态切换到"成功"状态
                                                   ④、请求失败后(计算机)：          应该将表单的状态切换到"失败"状态，与此同时，显示错误信息。
                                                   、

                                              为了可视化这个流程，请尝试在纸上画出"圆形标签"以表示每个状态，两个状态之间的改变用"箭头"表示。
                                              你可以像这样画出很多流程并且在写代码之前就解决很多bug
                                          */
                        

                              // 3、通过 "useSate" 表示内存中的状态(state)
                                          /*
                                              接下来你会需要在内存中通过useSate表示组件中的视图状态。
                                              诀窍很简单：   状态(state)的每个部分都是"处于变化中的"，并且你需要让"变化的部分"尽可能的少。
                                          */
                                           
                                          /*
                                              先从"绝对必须"存在的状态开始。

                                              eg： 你需要存储输入的answer以及用于存储最后一个错误的error（如果存在的话）
                                          */
                                                  const [answer,setAnswer] = useState('');
                                                  const [error,setError] =useState('');

                                          /*
                                              接下来，你需要一个状态变量来代表你显示的那个可视状态。
                                              通常有多种方式在内存中表示它，因此你需要进行实验。

                                              如果你很难立即相处最好的办法，那就先从添加足够多的状态(state)开始，确保所有可能的视图状态啊都囊括其中：
                                          */
                                                  const [isEmpty,setIsEmpty] = useState(true);
                                                  const [isTyping,setIsTyping] = useState(false);
                                                  const [isSubmitting, setIsSubmitting] = useState(false);
                                                  const [isSuccess, setIsSuccess] = useState(false);
                                                  const [isError, setIsError] = useState(false);
                                            // 你最初的想法可能不是最好的，但是没关系，重构state也是步骤中的一部分！


                              // 4、删除任何不必要的状态变量
                                          /*
                                               你会想要避免状态内容里的重复，从而只需要关注那些必要的部分。
                                               花一点事件重构你的状态（state）结构，会让你的组件更容易被理解，减少重复并避免歧义。
                                               你的目的是"防止出现在内存中的状态(state)不代表任何你希望用户看到的有效UI情况"。

                                               这里有一些你可以问自己的，关于状态变量的问题：

                                                  ①、这个state是否会产生矛盾：   
                                                        例如， isTyping 与 isSubmitting 的状态不能同时为true. 矛盾的产生通常说明了这个state没有足够的约束条件、
                                                        两个布尔值有四种可能的组合，但是只有三种对应有效的状态。
                                                        为了将"不可能出现的状态"移除，你可以将"typing" "submitting" 和 "success"这三个中的其中一个与 status结合

                                                  ②、相同的信息是由已经在另一个state变量中存在？
                                                        另一个矛盾： isEmpty 和 istyping 不能同时为 true。
                                                        通过使它们成为独立的状态变量，可能会导致它们不同步并导致bug.
                                                        幸运的是，你可以移除isEmpty转而用message.length===0.

                                                  ③、 你是否可以通过另一个状态变量的相反值得到相同的信息？  
                                                        isError 是多余的，因为你可以检查　error !==null

                                                在清理之后，你只剩下3个(从原本的7个)必要的状态变量：
                                          */
                                                  const [answer,setAnswer] = useState('');
                                                  const [error,setErrot] =useState('');
                                                  const [status,setStatus] = useState('typing');
                                                  // 正是因为你不能再不破坏功能的情况下删除其中任何一个状态变量，因此你可以确定这些都是必要的。


                              // 5、最后，创建时间处理函数去设置状态变量。
                                        import { useState } from 'react';

                                        export default function Form() {
                                                    const [answer, setAnswer] = useState('');
                                                    const [error, setError] = useState(null);
                                                    const [status, setStatus] = useState('typing');

                                                    if (status === 'success') {
                                                      return <h1>That's right!</h1>
                                                    }

                                                    async function handleSubmit(e) {
                                                        e.preventDefault();
                                                        setStatus('submitting');
                                                        try {
                                                            await submitForm(answer);
                                                            setStatus('success');
                                                        } catch (err) {
                                                            setStatus('typing');
                                                            setError(err);
                                                        }
                                                    }

                                                    function handleTextareaChange(e) {
                                                      setAnswer(e.target.value);
                                                    }

                                                    return (
                                                      <>
                                                        <h2>City quiz</h2>
                                                        <p>
                                                          In which city is there a billboard that turns air into drinkable water?
                                                        </p>
                                                        <form onSubmit={handleSubmit}>
                                                          <textarea
                                                            value={answer}
                                                            onChange={handleTextareaChange}
                                                            disabled={status === 'submitting'}
                                                          />
                                                          <br />
                                                          <button disabled={
                                                            answer.length === 0 ||
                                                            status === 'submitting'
                                                          }>
                                                            Submit
                                                          </button>
                                                          {error !== null &&
                                                            <p className="Error">
                                                              {error.message}
                                                            </p>
                                                          }
                                                        </form>
                                                      </>
                                                    );
                                        }

                                        function submitForm(answer) {
                                            // Pretend it's hitting the network.
                                            return new Promise((resolve, reject) => {
                                              setTimeout(() => {
                                                let shouldError = answer.toLowerCase() !== 'lima'
                                                if (shouldError) {
                                                  reject(new Error('Good guess but a wrong answer. Try again!'));
                                                } else {
                                                  resolve();
                                                }
                                              }, 1500);
                                            });
                                        }


                                  /*
                                        尽管这些代码相对于最初的"命令式编程"的例子来说更长，但确实更加健壮。
                                        将所有的交互变为状态(state)的变化，可以让你避免之后"引入新的视图状态"后导致"现有的状态"被破坏。
                                        同时也是你在不必改变交互逻辑的情况下，更改每个状态对应的UI。
                                  */ 

                              /*
                                  "声明式编程"意味着为每个视图状态声明UI而非细致地去控制UI(命令式)。
                                  （"声明式"可以理解为，你要什么，我马上给你创造出新的，"命令式"可以理解为，你要的我用现有的给你改出来。）

                                  当开发一个组件时：
                                        ①、写出你组件中所有的视图状态
                                        ②、确定是什么触发了这些状态的变化(认为输入、计算机输入)
                                        ③、通过useState模块化内存中的状态
                                        ④、删除任何不必要的状态变量
                                        ⑤、连接事件处理程序去设置状态变量
                              */


    </script>
  </body>
</html>